package jstart.exe;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.ArrayList;
import java.util.List;
import java.util.jar.JarEntry;
import java.util.jar.JarInputStream;

/**
 * 参考WinRun4实现的嵌入到可可执行为文件中的类加载器
 * {@link } <a href="https://github.com/poidasmith/winrun4j/blob/master/org.boris.winrun4j.classloader/java/src/org/boris/winrun4j/classloader/EmbeddedClassLoader.java">EmbeddedClassLoader.java</a>
 *
 * @author zwz
 * Created on 2021-02-21
 */
public class EmbeddedClassLoader extends URLClassLoader {

    private static List<String> names;
    private static List<byte[]> bytes;

    public EmbeddedClassLoader() {
        super(new URL[0], Thread.currentThread().getContextClassLoader());
    }

    @Override public InputStream getResourceAsStream(String name) {
        int index = findBytes(name);
        return index >= 0 ? new ByteArrayInputStream(bytes.get(index)) : null;
    }

    @Override protected Class<?> findClass(String name) throws ClassNotFoundException {
        String className = name.replace('.', '/').concat(".class");
        // System.out.println("find class: " + className);
        int index = findBytes(className);
        if (index >= 0) {
            // System.out.println(className + " Found!");
            byte[] byteCodes = bytes.get(index);
            names.remove(index);
            bytes.remove(index);
            return defineClass(name, byteCodes, 0, byteCodes.length);
        }
        throw new ClassNotFoundException(className);
    }

    @Override public URL findResource(String name) {
        try {
            return new URL("res:///" + name);
        } catch (MalformedURLException ignored) {
            return super.findResource(name);
        }
    }

    @SuppressWarnings("unused") // used by jni or sub classes
    void loadEmbeddedJar(byte[] jarBytes) throws IOException {
        if (names != null) return; // load only once!

        names = new ArrayList<>();
        bytes = new ArrayList<>();
        JarInputStream jis = new JarInputStream(new ByteArrayInputStream(jarBytes));
        JarEntry entry;
        int len;
        byte[] buffer = new byte[4096]; // 4k
        while ((entry = jis.getNextJarEntry()) != null) {
            if (entry.isDirectory()) continue; // skip directory
            ByteArrayOutputStream out = new ByteArrayOutputStream(4096);
            while ((len = jis.read(buffer)) > 0) {
                out.write(buffer, 0, len);
            }
            names.add(entry.getName());
            bytes.add(out.toByteArray());
        }
        jis.close();
    }

    private static int findBytes(String name) {
        for (int i = 0, len = names.size(); i < len; i++) {
            if (names.get(i).equals(name)) {
                return i;
            }
        }
        return -1;
    }
}
